package org.hawk.gwt.ppc.utils;

import java.util.regex.Pattern;

import org.hawk.gwt.ppc.reflect.lang.SystemClassLoader;

/**
 * Holds utilities for different kinds of naming conversions.
 * @author alex.bereznevatiy@gmail.com
 */
public class Name {
	private static final Pattern PKG_PART_PATTERN = 
		Pattern.compile("^([A-Za-z$_][$_\\w]*.)*[A-Za-z$_][$_\\w]*$");
	
	public static final String JAVA_SRC_EXTENSION = ".java";
	public static final String JAVA_PKG_INFO = "package-info.java";
	public static final char PACKAGE_SEPARATOR = '.';
	public static final char RESOURCE_SEPARATOR = '/';
	public static final char PARAMETER_TYPES_OPEN_CHAR = '<';
	public static final char PARAMETER_TYPES_CLOSE_CHAR = '>';
	public static final char ARRAY_OPEN_CHAR = '[';
	public static final char ANONIMOUS_TYPE_SEPARATOR = '$';
	
	private Name(){}
	/**
	 * Converts java class name to relative path in
	 * the file system. Uses java source extension to perform
	 * conversion.
	 * @param name
	 * @return java source relative path
	 */
	public static String javaToFs(String name){
		return javaToFs(name,JAVA_SRC_EXTENSION);
	}
	/**
	 * Converts java source file name to related simple class name.
	 * @param file name
	 * @return simple class name
	 */
	public static String fsToJava(String name) {
		if(!isJavaSrc(name)){
			throw new IllegalArgumentException(name+
					" is not a valid java source");
		}
		return name.substring(0,
			name.lastIndexOf(JAVA_SRC_EXTENSION));
	}
	/**
	 * Converts java name to relative path in
	 * the file system. Uses specified extension to perform
	 * conversion.
	 * @param name
	 * @param ext
	 * @return resource relative path
	 */
	public static String javaToFs(String name, String ext){
		StringBuilder result = new StringBuilder();
		result.append(name.replace(PACKAGE_SEPARATOR, RESOURCE_SEPARATOR));
		if(ext.length()!=0&&ext.charAt(0)!='.'){
			result.append('.');
		}
		result.append(ext);
		return result.toString();
	}
	/**
	 * @param name
	 * @return <code>true</code> if file name 
	 * is real java source file.
	 */
	public static boolean isJavaSrc(String name) {
		return name.toLowerCase().endsWith(JAVA_SRC_EXTENSION)&&
			!name.equalsIgnoreCase(JAVA_PKG_INFO);
	}
	/**
	 * @param packageName
	 * @return <code>true</code> if specified package name 
	 * is valid and <code>false</code> otherwise.
	 */
	public static boolean isValidPackageName(String packageName) {
		if(packageName==null)return false;
		if(packageName.length()==0)return true;
		return PKG_PART_PATTERN.matcher(packageName).matches();
	}
	/**
	 * Creates valid name for subelement (class or subpackage) 
	 * of passed parent package.
	 * @param parent
	 * @param name
	 * @return name of class or subpackage.
	 */
	public static String getChildJavaName(String parent, String name) {
		if(parent==null||parent.length()==0){
			return name;
		}
		return parent+PACKAGE_SEPARATOR+name;
	}
	/**
	 * @param name
	 * @return parent package or class of package or class
	 * represented by the name passed.
	 */
	public static String getParentJavaName(String name) {
		if(name==null||name.length()==0)return "";
		int i = name.lastIndexOf(PACKAGE_SEPARATOR);
		if(i<0)return "";
		return name.substring(0,i);
	}
	/**
	 * @param cls
	 * @return simple name for fully qualified java name.
	 */
	public static String getSimpleName(String cls) {
		int i = cls.lastIndexOf(PACKAGE_SEPARATOR);
		if(i<0)return cls;
		return cls.substring(i+1);
	}
	/**
	 * @param pkg
	 * @param parent
	 * @return true of pkg is direct subpackage for parent.
	 */
	public static boolean isDirectSubPackage(String pkg, String parent) {
		if(parent==null||pkg==null||
			pkg.length()==0||
			!pkg.startsWith(parent)){
			return false;
		}
		int len = parent.length();
		if(len==0){
			return pkg.indexOf(PACKAGE_SEPARATOR)<0;
		}
		len++;
		if(pkg.length()<=len||
			pkg.charAt(len-1)!=PACKAGE_SEPARATOR){
			return false;
		}
		return pkg.indexOf(PACKAGE_SEPARATOR,len)<0;
	}
	/**
	 * Return common package (path) for two java names.
	 * If names belongs to different package trees - returns empty
	 * string.
	 * @param name1
	 * @param name2
	 * @return common package or empty string if there is no common
	 * package (only default package is common for all other packages)
	 */
	public static String getCommonPackage(String name1, String name2) {
		if(name1==name2)return name1;
		if(name1.length()>name2.length()){
			return getCommonPackage(name2,name1);
		}
		int checkPoint = 0;
		final int LEN = name1.length();
		for(int i=0;i<LEN;i++){
			final char c = name1.charAt(i);
			if(name2.charAt(i)!=c){
				return name1.substring(0,checkPoint); 
			}
			if(c==PACKAGE_SEPARATOR){
				checkPoint = i;
			}
		}
		if(name2.length()>LEN&&
			name2.charAt(LEN)!=PACKAGE_SEPARATOR){
			return name1.substring(0,checkPoint);
		}
		return name1;
	}
	/**
	 * @param type
	 * @return <code>true</code> if the name passed is the fully qualified
	 * java name. Note that names of classes from default package 
	 * will be treated as simple names.
	 */
	public static boolean isFullName(String type) {
		return type!=null&&type.indexOf(PACKAGE_SEPARATOR)>=0;
	}
	/**
	 * @param type
	 * @return name of the class without type parameters.
	 * If type doesn't have a parameters specified - returns
	 * name passed.
	 */
	public static String getRawName(String type) {
		int i = type.indexOf(PARAMETER_TYPES_OPEN_CHAR);
		int j = type.lastIndexOf(PARAMETER_TYPES_CLOSE_CHAR);
		if(i < 0) {
			return type;
		}
		if (j < 0) {
			return type.substring(0,i);
		}
		return type.substring(0,i) + type.substring(j + 1);
	}
	/**
	 * @param type
	 * @return type parameters of the class or empty string
	 * if there aren't any.
	 */
	public static String getTypeParameters(String type) {
		int i = type.indexOf(PARAMETER_TYPES_OPEN_CHAR);
		if(i<0)return "";
		return type.substring(i);
	}
	/**
	 * @param type
	 * @return name of type without type parameters and array definition.
	 * This is the name of class from source itself. 
	 */
	public static String getPureClassName(String type) {
		int j = type.indexOf(ARRAY_OPEN_CHAR);
		if (j < 0) {
			return getRawName(type);
		}
		if (j == 0) {
			int l = type.indexOf('L');
			if (l < 0) {
				return SystemClassLoader.getArrayComponentName(type);
			}
			return type.substring(l, type.length() - 1).replace(RESOURCE_SEPARATOR, PACKAGE_SEPARATOR);
		}
		return type.substring(0, j);
	}
	/**
	 * @param token
	 * @return <code>true</code> only if passed token 
	 * is valid java identifier.
	 */
	public static boolean isJavaName(String token) {
		char[] name = token.toCharArray();
		if(!Character.isJavaIdentifierStart(name[0])){
			return false;
		}
		for(int i=1;i<name.length;i++){
			if(!Character.isJavaIdentifierPart(name[i])){
				return false;
			}
		}
		return true;
	}
}
